#Region "Using Directives"

Imports System
Imports System.Text

#End Region

Namespace C4F.DevKit.Bluetooth

    Namespace ServiceAndDeviceDiscovery

        ''' <summary>
        ''' Represents a service which can be advertisized on local device.       
        ''' </summary>
        Public MustInherit Class LocalService
            Inherits Service

#Region "Protected Members"

            ''' <summary>
            ''' Socket used for connecting to this service.
            ''' </summary>
            Protected socket As Net.Sockets.Socket

            ''' <summary>
            ''' Structure containing service specific information.
            ''' </summary>
            Protected wsaq As QuerySet

#End Region

#Region "Public Functions"

            ''' <summary>
            ''' Initializes a Local Service object.
            ''' </summary>
            Public Sub New()

                MyBase.New()

            End Sub

            ''' <summary>
            ''' Advertisizes and Registers the service on local device.
            ''' </summary>        
            ''' <param name="type">Type of the service to be advertisized.
            ''' </param>
            ''' <param name="name">Name of the service to be advertisized.
            ''' </param>
            ''' <param name="description">Description of the servie to be 
            ''' advertisized.</param>
            ''' <returns>ServiceHandle object which allows the end-user to  
            ''' control connections to this service. </returns>
            ''' <exception cref="ServiceRegistrationException">Thrown when 
            ''' an error occurs while registering the service.</exception>
            ''' <exception cref="ArgumentNullException">Thrown when 
            ''' a null reference (not set or empty) is passed 
            ''' to a method that does not accept it as a valid argument.
            ''' </exception>            
            Public Overridable Function Register(ByVal type As ServiceType, ByVal name As String, ByVal description As String) As ServiceHandle

                ' Verify if type of service is valid.
                If type Is Nothing Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgNullServiceType)
                End If

                ' Verify if name of service is valid.
                If String.IsNullOrEmpty(name) Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgNullName)
                End If

                ' Verify if description of service is valid.
                If String.IsNullOrEmpty(description) Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgNullDescription)
                End If

                Try
                    ' Create the socket.
                    Me.socket = New Net.Sockets.Socket(DirectCast(32, Net.Sockets.AddressFamily), Net.Sockets.SocketType.Stream, Net.Sockets.ProtocolType.Ggp)

                    Dim localAddr As New BluetoothSocketAddress()

                    ' Create a Bluetooth endpoint.
                    Dim btep As New BluetoothEndPoint(localAddr, type.ServiceGuid)

                    ' Bind the socket to the endpoint.
                    Me.socket.Bind(btep)

                    ' Extract port number.
                    Dim port As Integer = DirectCast(Me.socket.LocalEndPoint, BluetoothEndPoint).Port

                    ' Create a Service Record.
                    Dim sdpRecord() As Byte = GenerateSDPRecord(type, port, name, description)

                    ' Setup the BthSetService structure.
                    Dim sdpVersion As Integer = BluetoothHelper.BTH_SDP_VERSION

                    Dim bthService As New BthSetService()

                    Dim hSdpVersion As IntPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(GetType(Integer)))

                    System.Runtime.InteropServices.Marshal.WriteInt32(hSdpVersion, sdpVersion)

                    Dim hService As IntPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(GetType(IntPtr)))

                    System.Runtime.InteropServices.Marshal.WriteIntPtr(hService, IntPtr.Zero)

                    With bthService

                        .SdpVersion = hSdpVersion
                        .RecordHandle = hService
                        .RecordLength = CUInt(sdpRecord.Length)
                        bthService.Record = sdpRecord

                    End With

                    ' Setup the blob structure which contains a pointer to the
                    ' BthSetService structure.
                    Dim pService As IntPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(bthService))

                    System.Runtime.InteropServices.Marshal.StructureToPtr(bthService, pService, False)

                    Dim blob As New Blob()
                    blob.Size = CUInt(System.Runtime.InteropServices.Marshal.SizeOf(bthService))

                    blob.BlobData = pService

                    ' Setup the queryset structure which contains information 
                    ' specific to the service.
                    With Me

                        .wsaq = New QuerySet()
                        .wsaq.Size = System.Runtime.InteropServices.Marshal.SizeOf(Me.wsaq)
                        .wsaq.[NameSpace] = BluetoothHelper.NS_BTH

                    End With

                    Dim pBlob As IntPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(blob))
                    System.Runtime.InteropServices.Marshal.StructureToPtr(blob, pBlob, False)
                    Me.wsaq.Blob = pBlob
                    Dim ptrServiceClassId As IntPtr = System.Runtime.InteropServices.Marshal.AllocHGlobal(System.Runtime.InteropServices.Marshal.SizeOf(type.ServiceGuid))

                    System.Runtime.InteropServices.Marshal.StructureToPtr(type.ServiceGuid, ptrServiceClassId, True)
                    Me.wsaq.ServiceClassId = ptrServiceClassId

                    ' Register the service.
                    Dim result As Integer = 0
                    result = BluetoothHelper.WSASetService(Me.wsaq, WsaSetServiceOp.Register, 0)

                    If result <> 0 Then
                        BluetoothHelper.GetError()
                    Else
                        With Me

                            .m_description = description
                            .m_id.ServiceIdType = ServiceIdType.TypeNil
                            .m_id.ServiceIdUnion = New ServiceIdUnion()
                            .m_name = name
                            .m_serviceRecord = sdpRecord
                            .m_type = type

                        End With
                    End If

                    ' SerialPort Profile can service only one client at a time.
                    If type.ServiceGuid = BluetoothHelper.SERIAL_PORT_PROFILE Then
                        Me.socket.Listen(1)
                    Else
                        Me.socket.Listen(100)
                        ' For the rest accept more than one connection.
                    End If
                    Return New ServiceHandle(Me.socket)

                Catch winSockEx As WinsockException
                    Dim servEx As New ServiceRegistrationException(SDPMessages.ErrorMsgServiceRegistration + winSockEx.Message)
                    Throw servEx

                Catch ex As Exception
                    Dim servEx As New ServiceRegistrationException(SDPMessages.ErrorMsgServiceRegistration + ex.Message)
                    Throw servEx

                End Try

            End Function

            ''' <summary>
            ''' Stops advertizing the service.
            ''' </summary>      
            ''' <exception cref="ServiceRegistrationException">Thrown when 
            ''' an error occurs while unregistering the service.</exception>
            Public Overridable Sub UnRegister()

                Try
                    If Me.wsaq IsNot Nothing Then
                        If Me.socket IsNot Nothing Then
                            Me.socket.Close()
                        End If

                        ' Un-registers the service.
                        Dim result As Integer = BluetoothHelper.WSASetService(Me.wsaq, WsaSetServiceOp.Delete, 0)

                        If result <> 0 Then
                            BluetoothHelper.GetError()
                        End If
                    Else
                        Throw New ServiceRegistrationException(SDPMessages.ErrorMsgServiceNotRegistered)
                    End If

                Catch ex As Exception
                    If TypeOf ex Is ServiceRegistrationException Then
                        Throw ex
                    End If

                    If TypeOf ex.InnerException Is ServiceRegistrationException Then
                        Throw ex.InnerException
                    End If

                    Dim servEx As New ServiceRegistrationException(SDPMessages.ErrorMsgServiceNotRegistered + ex.Message)
                    Throw servEx

                Finally
                    Me.wsaq = Nothing
                End Try

            End Sub


            ''' <summary>
            ''' Creates a service record which describes the service. 
            ''' The arguments and their values are used in creating the service record.
            ''' </summary>
            ''' <param name="type">Type of the service.</param>
            ''' <param name="port">Port for the service.</param>
            ''' <param name="name">Name of the service.</param>
            ''' <param name="description">Description of the service.</param>
            ''' <returns>Returns the service record of the service.</returns>
            ''' <exception cref="ServiceRegistrationException">Thrown when 
            ''' an error occurs while generating Service Record.</exception>  
            ''' <exception cref="ArgumentNullException">Thrown when 
            ''' when a null reference (not set or empty) is passed 
            ''' to a method that does not accept it as a valid argument.
            ''' </exception>           
            Public Overridable Function GenerateSDPRecord(ByVal type As ServiceType, ByVal port As Integer, ByVal name As String, ByVal description As String) As Byte()

                ' Verify if type is valid.
                If type Is Nothing Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgNullServiceType)
                End If

                ' Verify if name is valid.
                If String.IsNullOrEmpty(name) Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgNullName)
                End If

                ' Verify if description is valid.
                If String.IsNullOrEmpty(description) Then
                    Throw New ArgumentNullException(SDPMessages.ErrorMsgNullDescription)
                End If

                Try
                    Dim encoding As New System.Text.ASCIIEncoding()

                    Dim serviceNameArray() As Byte = Nothing
                    Dim serviceDescArray() As Byte = Nothing

                    serviceNameArray = encoding.GetBytes(name)
                    serviceDescArray = encoding.GetBytes(description)

                    ' Service record for the service.
                    Dim sdpRecord() As Byte = _
                    { _
                        53, 124, 9, 0, 1, 53, _
                        17, 28, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        9, 0, 4, 53, 12, 53, _
                        3, 25, 1, 0, 53, 5, _
                        25, 0, 3, 8, 0, 9, _
                        1, 0, 37, 25, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 9, _
                        1, 1, 37, 50, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0, _
                        0, 0, 0, 0, 0, 0 _
                    }

                    ' Set sdp record length.
                    sdpRecord(1) = Convert.ToByte(sdpRecord.Length - 2)

                    ' Copy in the guid of the type of service in network byte order.
                    Dim guidArray() As Byte = type.ServiceGuid.ToByteArray()

                    sdpRecord(8) = guidArray(3)
                    sdpRecord(9) = guidArray(2)
                    sdpRecord(10) = guidArray(1)
                    sdpRecord(11) = guidArray(0)

                    sdpRecord(12) = guidArray(5)
                    sdpRecord(13) = guidArray(4)

                    sdpRecord(14) = guidArray(7)
                    sdpRecord(15) = guidArray(6)

                    Array.Copy(guidArray, 8, sdpRecord, 16, 8)

                    sdpRecord(40) = Convert.ToByte(port)

                    Dim serviceNameLength As Integer = 0
                    Dim serviceDescLength As Integer = 0

                    If serviceNameArray.Length <= 25 Then
                        serviceNameLength = serviceNameArray.Length
                    Else
                        serviceNameLength = 25
                    End If

                    If serviceDescArray.Length <= 50 Then
                        serviceDescLength = serviceDescArray.Length
                    Else
                        serviceDescLength = 50
                    End If
                    For i As Integer = 0 To serviceNameLength - 1
                        sdpRecord(46 + i) = serviceNameArray(i)
                    Next
                    For i As Integer = 0 To serviceDescLength - 1
                        sdpRecord(76 + i) = serviceDescArray(i)
                    Next

                    Return sdpRecord
                Catch
                    Dim servEx As New ServiceRegistrationException(SDPMessages.ErrorMsgGenSdpRecord)

                    Throw servEx
                End Try

            End Function

#End Region

        End Class

    End Namespace

End Namespace
